package space.lichenpark.unfamiliar.components.influx.core;

import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import space.lichenpark.unfamiliar.components.influx.util.RangeUtil;

import java.util.*;


/**
 * Flux 查询封装
 *
 * @author lichen
 */
public class FluxQueryWrapper {
    /**
     * 存储桶
     */
    private String bucket;
    /**
     * 时间范围
     * <p>
     * 可以直接输入开始和结束时间，也可以调用TimeRangeUtil快速获取最近一段时间
     */
    private Range range;
    /**
     * 过滤条件
     */
    private Filter filter = new Filter();

    /**
     * 聚合函数
     * <p>
     * 组件自带了常用的几种聚合函数，如不能满足可以实现AggregateFunction接口进行自定义，或者提交issue讨论
     */
    private FnEnum fn;

    /**
     * 聚合窗口
     */
    private AggregateWindow aggregateWindow;


    /**
     * 时区偏移
     * <p>
     * 默认值 8，代表东8区；西8区是 -8
     */
    private int timeShift = 8;


    public FluxQueryWrapper fromBucket(String bucket) {
        this.bucket = bucket;
        return this;
    }

    public FluxQueryWrapper timeShift(int timeShift) {
        this.timeShift = timeShift;
        return this;
    }

    public FluxQueryWrapper range(Date startTime, Date endTime) {
        this.range = RangeUtil.getRange(startTime, endTime);
        return this;
    }

    public FluxQueryWrapper range(String startTime, String endTime, String format) {
        this.range = RangeUtil.getRange(startTime, endTime, format);
        return this;
    }

    public FluxQueryWrapper range(Range range) {
        this.range = range;
        return this;
    }

    public FluxQueryWrapper inMeasurement(String... measurements) {
        this.filter.getInMeasurements().addAll(List.of(measurements));
        return this;
    }

    public FluxQueryWrapper notInMeasurement(String... measurements) {
        this.filter.getNotInMeasurements().addAll(List.of(measurements));
        return this;
    }

    public FluxQueryWrapper inMeasurement(String measurements) {
        this.filter.getInMeasurements().add(measurements);
        return this;
    }

    public FluxQueryWrapper notInMeasurement(String measurements) {
        this.filter.getNotInMeasurements().add(measurements);
        return this;
    }

    public FluxQueryWrapper inTag(String key, String... value) {
        this.filter.getInTags().put(key, List.of(value));
        return this;
    }

    public FluxQueryWrapper inTag(String key, String value) {
        Collection<String> tagValues = this.filter.getInTags().get(key);
        if (tagValues == null) {
            tagValues = new ArrayList<>();
            tagValues.add(value);

        } else if (!tagValues.contains(value)) {
            tagValues.add(value);
        }
        this.filter.getInTags().put(key, tagValues);
        return this;
    }


    public FluxQueryWrapper field(String field) {
        if (!this.filter.getField().contains(field)) {
            this.filter.getField().add(field);
        }
        return this;
    }

    public FluxQueryWrapper field(String... fields) {
        this.filter.getField().addAll(List.of(fields));
        return this;
    }

    public FluxQueryWrapper notInTag(String key, String value) {
        Collection<String> tagValues = this.filter.getNotInTags().get(key);
        if (tagValues == null) {
            tagValues = new ArrayList<>();
            tagValues.add(value);
        } else if (!tagValues.contains(value)) {
            tagValues.add(value);
        }
        return this;
    }

    public FluxQueryWrapper notInTag(String key, String... value) {
        this.filter.getNotInTags().put(key, List.of(value));
        return this;
    }

    public FluxQueryWrapper fn(FnEnum fn) {
        this.fn = fn;
        return this;
    }

    public FluxQueryWrapper aggregateWindow(String every, FnEnum fn, boolean createEmpty) {
        this.aggregateWindow = new AggregateWindow(every, fn, createEmpty);
        return this;
    }

    /**
     * 构建查询语句
     *
     * @return String
     */
    public String buildQueryString() {
        StringBuilder queryStr = new StringBuilder();
        queryStr.append("from(bucket: \"").append(this.bucket).append("\")");
        queryStr.append(" |> range(start: ").append(this.range.getStartTime()).append(", stop: ").append(this.range.getEndTime()).append(")");
        if (!filter.getInMeasurements().isEmpty()) {
            String or = String.join("\" or r[\"_measurement\"] == \"", filter.getInMeasurements());
            queryStr.append("  |> filter(fn: (r) => r[\"_measurement\"] == \"").append(or).append("\")");
        }
        if (!filter.getNotInMeasurements().isEmpty()) {
            String and = String.join("\" and r[\"_measurement\"] != \"", filter.getNotInMeasurements());
            queryStr.append("  |> filter(fn: (r) => r[\"_measurement\"] != \"").append(and).append("\")");
        }
        if (!filter.getInTags().isEmpty()) {
            filter.getInTags().forEach((key, value) -> {
                queryStr.append("  |> filter(fn: (r) =>  r[\"").append(key).append("\"] == \"")
                        .append(String.join("\" or r[\"" + key + "\"] == \"", value)).append("\")");
            });
        }
        if (!filter.getNotInTags().isEmpty()) {
            filter.getNotInTags().forEach((key, value) -> {
                queryStr.append("  |> filter(fn: (r) =>  r[\"").append(key).append("\"] != \"")
                        .append(String.join("\" and r[\"" + key + "\"] != \"", value)).append("\")");
            });
        }
        if (!filter.getField().isEmpty()) {
            String or = String.join("\" or r[\"_field\"] == \"", filter.getField());
            queryStr.append("  |> filter(fn: (r) => r[\"_field\"] == \"").append(or).append("\")");
        }
        if (fn != null) {
            queryStr.append(fn.getDisplayForStandAlone());
        }
        if (aggregateWindow != null && StrUtil.isNotEmpty(aggregateWindow.getEvery())) {
            if (aggregateWindow.getFn().isOnlyStandAlone()) {
                String msg = StrFormatter.format("fn {} can not be used in aggregate window.", aggregateWindow.getFn().getDisplayInAggregateWindow());
                throw new RuntimeException(msg);
            }
            queryStr.append("  |> aggregateWindow(every: ").append(aggregateWindow.getEvery()).append(", fn: ").append(aggregateWindow.getFn().getDisplayInAggregateWindow()).append(", createEmpty: ").append(aggregateWindow.isCreateEmpty()).append(")");
        }
        // 时区
        queryStr.append("|> timeShift(duration: " + timeShift + "h)");

        return queryStr.toString();
    }

    /**
     * 时间范围
     *
     * @author Lichenpark
     */

    @Getter
    @Setter
    @AllArgsConstructor
    public static class Range {
        /**
         * 开始时间
         */
        private String startTime;
        /**
         * 结束时间
         */
        private String endTime;
    }

    /**
     * 筛选器
     *
     * @author Lichenpark
     */

    @Getter
    @Setter
    public static class Filter {
        /**
         * 筛选表
         */
        private Collection<String> inMeasurements = new ArrayList<>();
        /**
         * 反向筛选表
         */
        private Collection<String> notInMeasurements = new ArrayList<>();
        /**
         * 筛选标签
         */
        private Map<String, Collection<String>> inTags = new HashMap<>();
        /**
         * 反向筛选标签
         */
        private Map<String, Collection<String>> notInTags = new HashMap<>();
        /**
         * 筛选字段
         */
        private Collection<String> field = new ArrayList<>();
    }

    /**
     * 聚合窗口
     */
    @Getter
    @Setter
    @AllArgsConstructor
    public static class AggregateWindow {
        /**
         * 聚合窗口的间隔
         */
        private String every;
        /**
         * 聚合窗口的聚合函数
         */
        private FnEnum fn;
        /**
         * 聚合窗口是否创建空值
         */
        private boolean createEmpty;
    }
}